﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI;
using PostSharp.CodeWeaver;
using PostSharp.CodeModel;
using PostSharp.CodeModel.Helpers;
using PostSharp.Collections;
using System.Reflection;

namespace PostSharp4ViewState.Weaver
{
   sealed class RegisterControlStateAdvice : PersistAdvice
   {
      public RegisterControlStateAdvice(PersistTask parent, PerTypeData data)
         : base(parent, PersistMode.ControlState, "OnInit", data)
      {
      }

      public override JoinPointKinds JointPointKind
      {
         get { return JoinPointKinds.BeforeMethodBody; }
      }

      protected override void DoCreateMethod(MethodDefDeclaration methodDef)
      {
         methodDef.Name = _methodName;
         methodDef.Attributes = MethodAttributes.Virtual | MethodAttributes.Family | MethodAttributes.HideBySig;
         methodDef.CallingConvention = CallingConvention.HasThis;

         methodDef.ReturnParameter = new ParameterDeclaration();
         methodDef.ReturnParameter.Attributes = ParameterAttributes.Retval;
         methodDef.ReturnParameter.ParameterType = _parent.VoidType;

         ParameterDeclaration param = new ParameterDeclaration();
         param.ParameterType = _parent.EventArgsType;
         methodDef.Parameters.Add(param);
         MethodBodyDeclaration methodBody = new MethodBodyDeclaration();
         methodDef.MethodBody = methodBody;
         methodBody.RootInstructionBlock = methodBody.CreateInstructionBlock();
      }

      private void WeavePrivate(MethodDefDeclaration wovenMethod, InstructionWriter writer, InstructionBlock block, bool exists)
      {
         InstructionSequence seqBefore = wovenMethod.MethodBody.CreateInstructionSequence();
         block.AddInstructionSequence(seqBefore, NodePosition.Before, null);
         writer.AttachInstructionSequence(seqBefore);
         writer.EmitSymbolSequencePoint(SymbolSequencePoint.Hidden);
         writer.EmitInstruction(OpCodeNumber.Nop);

         writer.EmitInstruction(OpCodeNumber.Ldarg_0);
         IMethod getPageMethod = GetInheritedMethod(_parent.ControlType.GetTypeDefinition(), "get_Page",
                                                    writer.MethodBody.Module);
         writer.EmitInstructionMethod(OpCodeNumber.Callvirt, getPageMethod);


         writer.EmitInstruction(OpCodeNumber.Ldarg_0);
         IMethod registerRequiresControlStateMethod =
            GetInheritedMethod(_parent.PageType.GetTypeDefinition(), "RegisterRequiresControlState",
                               writer.MethodBody.Module);
         writer.EmitInstructionMethod(OpCodeNumber.Callvirt, registerRequiresControlStateMethod);

         if (!exists)
         {
            writer.EmitInstruction(OpCodeNumber.Ldarg_0);
            writer.EmitInstruction(OpCodeNumber.Ldarg_1);

            IMethod baseMethod = GetInheritedMethod(wovenMethod.DeclaringType.BaseType.GetTypeDefinition(), _methodName, writer.MethodBody.Module);
            writer.EmitInstructionMethod(OpCodeNumber.Call, baseMethod);
            wovenMethod.Attributes |= (baseMethod.GetMethodDefinition().Attributes & (MethodAttributes.Public | MethodAttributes.FamANDAssem));
            writer.EmitInstruction(OpCodeNumber.Ret);
         }
         writer.DetachInstructionSequence();
      }

      public override bool RequiresWeave(WeavingContext context)
      {
         return true;
      }

      public override void Weave(WeavingContext context, InstructionBlock block)
      {
         if (context.Method.Name == _methodName)
         {
            MethodDefDeclaration wovenMethod = context.Method;
            WeavePrivate(wovenMethod, context.InstructionWriter, block, true);
         }
         else
         {
            MethodDefDeclaration wovenMethod = context.Method.DeclaringType.Methods.GetByName(_methodName).FirstOrDefault();
            WeavePrivate(wovenMethod, context.InstructionWriter, wovenMethod.MethodBody.RootInstructionBlock, false);
         }
      }
   }
}
